﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using EnterpriseLibraryExtensions.Events.Aggregation;
using Microsoft.Practices.Unity;

namespace EnterpriseLibraryExtensions.Events
{
    /// <summary>
    /// The event manager implements both the IEventRegistration and IEventProducer interfaces which
    /// can be used by consuming code depending on their needs.
    /// </summary>
    public sealed class EventManager : IEventRegistration, IEventProducer
    {
        /// <summary>
        /// The Unity Container used to store all event handlers.
        /// </summary>
        private readonly IUnityContainer _unityContainer;

        /// <summary>
        /// Initializes a new instance of the EventManager class. The event manager will use the provided unity container to
        /// resolve registered event handlers.
        /// </summary>
        /// <param name="unityContainer">The unity container to resolve event handlers from. This value cannot be null.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if unityContainer parameter is null.</exception>
        internal EventManager(IUnityContainer unityContainer)
        {
            if (unityContainer == null)
            {
                throw new ArgumentNullException("unityContainer");
            }

            _unityContainer = unityContainer;

            EventsTracer.TraceInformation("EventManager constructed with valid Unity container.");
        }

        /// <summary>
        /// This method registers the two interfaces <see cref="IEventProducer"/> and <see cref="IEventRegistration"/>
        /// on the unityContainer provided as parameter.
        /// </summary>
        /// <param name="unityContainer">The unity container to register the eventing system on. Value cannot be null.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if unityContainer is null.</exception>
        public static void RegisterInContainer(IUnityContainer unityContainer)
        {
            if (unityContainer == null)
            {
                throw new ArgumentNullException("unityContainer");
            }

            var eventManager = new EventManager(unityContainer);

            unityContainer.RegisterInstance<IEventRegistration>(eventManager);
            unityContainer.RegisterInstance<IEventProducer>(eventManager);

            EventsTracer.TraceInformation("EventManager registered as IEventRegistration and IEventProducer in Unity container.");
        }

        /// <summary>
        /// Register a new event handler.
        /// </summary>
        /// <typeparam name="TEventType">The event type that the event handler supports.</typeparam>
        /// <typeparam name="TEventHandler">The event handler implementing IEventHandler(TEventType).</typeparam>
        void IEventRegistration.Register<TEventType, TEventHandler>()
        {
            _unityContainer.RegisterType<IHandlesEvent<TEventType>, TEventHandler>(Guid.NewGuid().ToString());

            EventsTracer.TraceInformation("EventHandler {0} registered for handling {1}", typeof(TEventHandler), typeof(TEventType));
        }

        /// <summary>
        /// Registers an inline event handler by passing an action than will be invoked instead of resolving a class.
        /// </summary>
        /// <typeparam name="TEventType">The type of event the action can handle.</typeparam>
        /// <param name="action">The action to be invoked as event handler.</param>
        void IEventRegistration.Register<TEventType>(Action<object, EventArgs<TEventType>> action)
        {
            _unityContainer.RegisterInstance<IHandlesEvent<TEventType>>(Guid.NewGuid().ToString(), new InlineEvent<TEventType>(action));

            EventsTracer.TraceInformation("Inline EventHandler {0} registered for handling {1}", action.Method.Name, typeof(TEventType));
        }

        /// <summary>
        /// Raises an event of the specified TEventType. If the event is not being raised inside an event scope, the event is passed
        /// to any registered event handlers at once. Otherwise it will not be passed along to registered event handlers until the
        /// scope has been disposed and Complete has been called on the scope.
        /// </summary>
        /// <typeparam name="TEventType">The type of event to raise.</typeparam>
        /// <param name="sender">The instance raising the event. The value can be null.</param>
        /// <param name="args">The arguments of type TEventType. Thus value cannot be null.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if args is null.</exception>
        void IEventProducer.Raise<TEventType>(object sender, TEventType args)
        {
            if (args == null)
            {
                throw new ArgumentNullException("args");
            }

            Trace.CorrelationManager.ActivityId = Guid.NewGuid();

            EventsTracer.TraceInformation("Raising event. Sender: {0}, Arguments: {1}", sender ?? "null", args);

            IEnumerable<IHandlesEvent<TEventType>> eventHandlers = ResolveEventHandlers<TEventType>();

            EventsTracer.TraceInformation("Event handlers resolved. Count: {0}", eventHandlers.Count());

            EventAggregationScope.Enlist(sender, args, eventHandlers);

            EventsTracer.TraceInformation("Sender and arguments enlisted to event handlers.");
        }

        /// <summary>
        /// Resolves all event handlers capable of handling a specific event type.
        /// </summary>
        /// <typeparam name="TEventType">The type of event handler to query for.</typeparam>
        /// <returns>An IEnumerable of event handlers.</returns>
        private IEnumerable<IHandlesEvent<TEventType>> ResolveEventHandlers<TEventType>()
        {
            return _unityContainer.ResolveAll<IHandlesEvent<TEventType>>();
        }
    }
}
